package com.iplant.aps.serviceImpl.utils.aps;

import com.iplant.aps.service.apsenum.aps.APSMsgTypes;
import com.iplant.aps.service.apsenum.aps.APSShiftPeriod;
import com.iplant.aps.service.apsenum.aps.APSTaskStatus;
import com.iplant.aps.service.po.aps.APSCapacityUsed;
import com.iplant.aps.service.po.aps.APSTaskPartPoint;
import com.iplant.aps.service.po.sch.APSDoneMinSchedule;
import com.iplant.aps.service.po.sch.APSMarginCapacityAndResult;
import com.iplant.aps.service.po.sch.APSMarginEntityCapacity;
import com.iplant.aps.service.po.sch.APSMessage;
import com.iplant.aps.service.po.aps.APSTaskPart;
import com.iplant.aps.service.po.oms.OMSOrder;
import com.iplant.aps.serviceImpl.dao.APSTaskPartDAO;
import com.iplant.base.po.OutResult;
import com.iplant.base.po.Pagination;
import com.iplant.base.utils.general.CalendarTool;
import com.iplant.base.utils.general.StringUtils;
import com.iplant.basic.service.po.bms.BMSEmployee;
import com.iplant.basic.service.po.cfg.CFGCalendar;
import com.iplant.basic.service.po.fmc.FMCShift;
import com.iplant.basic.service.po.fmc.FMCUnavailable;
import com.iplant.pdm.service.basicenum.fpc.FPCRouteType;
import com.iplant.pdm.service.po.bop.FPCRoute;
import com.iplant.pdm.service.po.bop.FPCRoutePart;
import com.iplant.pdm.service.po.bop.FPCRoutePartPoint;
import com.iplant.pdm.service.po.fpc.FPCCapacity;
import com.iplant.pdm.service.po.fpc.FPCProduct;
import com.iplant.pdm.service.po.mss.MSSBOMItem;
import com.iplant.wms.service.po.wms.WMSStock;
import com.iplant.wms.serviceImpl.dao.wms.WMSStockRecordDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

public class iPlantSchedulePartTools {
    private static final Logger logger = LoggerFactory.getLogger(iPlantSchedulePartTools.class);

    public iPlantSchedulePartTools() {
    }

    private static iPlantSchedulePartTools Instance;

    public static iPlantSchedulePartTools getInstance() {
        if (Instance == null)
            Instance = new iPlantSchedulePartTools();
        return Instance;
    }


    public List<APSTaskPart> GetStartedTaskList(BMSEmployee wLoginUser, List<OMSOrder> wOrderList,
                                                APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate,
                                                Calendar wEndDate,
                                                Map<Integer, List<FPCRoutePart>> wFPCRoutePartListMap,
                                                Map<Integer, List<List<FPCRoutePart>>> wMarginRoutePartListMap,
                                                Map<Integer, List<APSTaskPart>> wAPSTaskPartDoneListMap) {


        OutResult<Integer> wErrorCode = new OutResult<>(0);


        List<Integer> wOrderIDList = wOrderList.stream().map(p -> p.ID).distinct().collect(Collectors.toList());
        if (wOrderIDList.size() == 0)
            return new ArrayList<>();
        // 时间段内已排程计划
        List<APSTaskPart> wStartTaskList = APSTaskPartDAO.getInstance().GetAPSTaskPartList(wLoginUser,
                wAPSShiftPeriod, wStartDate,
                wEndDate, wErrorCode);


        Map<Integer, List<List<FPCRoutePart>>> wPartRouteListLineMap = new HashMap<Integer, List<List<FPCRoutePart>>>();
        for (Integer wRouteID : wFPCRoutePartListMap.keySet()) {

            FPCRoutePartUtils wFPCRoutePartUtils = new FPCRoutePartUtils(wFPCRoutePartListMap.get(wRouteID));
            wPartRouteListLineMap.put(wRouteID, wFPCRoutePartUtils.GetPartOrderRouteList());
        }


        switch (wAPSShiftPeriod) {
            case Day:
            case Week:
                List<APSTaskPart> wOrderPartIssuedList = APSTaskPartDAO.getInstance().GetAPSTaskPartList(wLoginUser,
                        wAPSShiftPeriod, wOrderIDList, wErrorCode);
                //周排程 1 删除已开工计划 未开工计划可以重排
                wAPSTaskPartDoneListMap.putAll(wOrderPartIssuedList.stream()
                        .filter(p -> p.Active == 1 && (p.Status == APSTaskStatus.Confirm.getValue()
                                || p.Status == APSTaskStatus.Started.getValue()
                                || p.Status == APSTaskStatus.Suspend.getValue() || p.Status == APSTaskStatus.Done.getValue()
                                || p.Status == APSTaskStatus.Aborted.getValue()))
                        .collect(Collectors.groupingBy(p -> p.OrderID)));

                break;
            case Month:
                //月排程 已开工订单不能重排月计划 1 删除已开工订单 未开工订单可以排程
                // wStartTaskList 删除待排订单的已排产计划；
                wStartTaskList.removeIf(p -> wOrderIDList.contains(p.OrderID));

                break;
            default:
                break;
        }
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wPartRouteListLineMap.containsKey(wOMSOrder.RouteID)) {
                wMarginRoutePartListMap.put(wOMSOrder.ID, new ArrayList<List<FPCRoutePart>>());
                continue;
            }
            wMarginRoutePartListMap.put(wOMSOrder.ID, FPCRoutePartUtils.GetUndoStationRouteList(wLoginUser
                    , wAPSTaskPartDoneListMap.containsKey(wOMSOrder.ID) ? wAPSTaskPartDoneListMap.get(wOMSOrder.ID) :
                            new ArrayList<>(),
                    wPartRouteListLineMap.get(wOMSOrder.RouteID)));
        }
        return wStartTaskList;
    }


    /**
     * 获取前置固定计划的最小批次
     *
     * @param wDonePart
     * @return
     */

    public List<APSDoneMinSchedule> GetPrevPartMinSchduleList(Map<Integer, APSTaskPart> wDonePart, float wPlanNum) {
        List<APSDoneMinSchedule> wResult = new ArrayList<APSDoneMinSchedule>();
        if (wDonePart == null || wDonePart.size() == 0) {
            return wResult;
        }


        Map<Integer, List<APSCapacityUsed>> wAPSCapacityUsedListMap = new HashMap<>();
        for (Integer wKey : wDonePart.keySet()) {

            wAPSCapacityUsedListMap.put(wKey, new ArrayList<>());

            if (wDonePart.get(wKey) == null || wDonePart.get(wKey).PlanDayDetailNum == null || wDonePart.get(wKey).PlanDayDetailNum.size() == 0)
                continue;

            for (String wDateString : wDonePart.get(wKey).PlanDayDetailNum.keySet()) {
                if (wDonePart.get(wKey).PlanDayDetailNum.get(wDateString) == null || wDonePart.get(wKey).PlanDayDetailNum.get(wDateString).size() == 0)
                    continue;
                for (APSCapacityUsed wAPSCapacityUsed : wDonePart.get(wKey).PlanDayDetailNum.get(wDateString)) {
                    wAPSCapacityUsedListMap.get(wKey).add(wAPSCapacityUsed.Clone());
                }
            }
            wAPSCapacityUsedListMap.get(wKey).sort(Comparator.comparing((APSCapacityUsed o) -> o.CurrentDate).thenComparing((APSCapacityUsed o) -> o.ShiftStartTime));
        }
        APSDoneMinSchedule wAPSDoneMinSchedule;
        int wIndex = 0;
        while (wPlanNum > 0) {


            float wMinNum = wPlanNum;
            for (Integer wKey : wAPSCapacityUsedListMap.keySet()) {

                if (wAPSCapacityUsedListMap.get(wKey).size() == 0)
                    continue;
                while (wAPSCapacityUsedListMap.get(wKey).size() > 0) {
                    if (wAPSCapacityUsedListMap.get(wKey).get(0).UsedCapacity <= 0) {
                        wAPSCapacityUsedListMap.get(wKey).remove(0);
                        continue;
                    }
                    break;
                }
                if (wAPSCapacityUsedListMap.get(wKey).size() == 0)
                    continue;

                if (wAPSCapacityUsedListMap.get(wKey).get(0).UsedCapacity < wMinNum)
                    wMinNum = wAPSCapacityUsedListMap.get(wKey).get(0).UsedCapacity;
            }
            wIndex++;

            wAPSDoneMinSchedule = new APSDoneMinSchedule();
            wAPSDoneMinSchedule.MinPlan = wMinNum;
            wAPSDoneMinSchedule.Index = wIndex;

            for (Integer wKey : wAPSCapacityUsedListMap.keySet()) {

                if (wAPSCapacityUsedListMap.get(wKey).size() == 0)
                    continue;

                if (wAPSCapacityUsedListMap.get(wKey).size() == 0)
                    continue;

                wAPSCapacityUsedListMap.get(wKey).get(0).UsedCapacity -= wMinNum;

                wAPSDoneMinSchedule.TechnologyList.put(wKey, wAPSCapacityUsedListMap.get(wKey).get(0).ShiftStartTime);
            }
            wPlanNum -= wMinNum;
        }

        return wResult;
    }


    /**
     * 根据排程结果创建工艺计划
     *
     * @param wLoginUser
     * @param wOMSOrder
     * @param wOrderRoutePartMap
     * @param wResultMap
     * @return
     */
    public List<APSTaskPart> CreateAPSTaskPartList(BMSEmployee wLoginUser, APSShiftPeriod wAPSShiftPeriod,
                                                   OMSOrder wOMSOrder, Map<Integer, FPCRoutePart> wOrderRoutePartMap,
                                                   Map<Integer, Map<String, List<APSCapacityUsed>>> wResultMap) {

        List<APSTaskPart> wResult = new ArrayList<>();

        if (wResultMap == null || wResultMap.size() == 0) {
            return wResult;
        }

        APSTaskPart wAPSTaskPart;
        FPCRoutePart wFPCRoutePart;
        int wEntityID = 0;
        for (int wRoutePartID : wOrderRoutePartMap.keySet()) {
            //无排程结果，continue
            wEntityID = wOrderRoutePartMap.get(wRoutePartID).getPartEntityID();
            if (wEntityID <= 0)
                continue;
            if (!wResultMap.containsKey(wEntityID)) {
                continue;
            }

            wFPCRoutePart = wOrderRoutePartMap.get(wRoutePartID);

            Map<String, List<APSCapacityUsed>> wDayFloatMap=new HashMap<>();

            for (String wDateStr : wResultMap.get(wEntityID).keySet()) {
                wDayFloatMap.put(wDateStr,wResultMap.get(wEntityID).get(wDateStr).stream().filter(p->p.PartPointID==wRoutePartID).collect(Collectors.toList()));
            }


            if (wDayFloatMap == null || wDayFloatMap.size() == 0)
                continue;

            Map<String, Float> wDayMap = new HashMap<>();

            Calendar wMaxCalendar = CalendarTool.getCalendar(2000, 0, 1);
            ;
            Calendar wMinCalendar = CalendarTool.getCalendar(2100, 0, 1);
            Calendar wCalendarT = Calendar.getInstance();
            for (String wDayStr : wDayFloatMap.keySet()) {
                wDayMap.put(wDayStr, (float) wDayFloatMap.get(wDayStr).stream().mapToDouble(p -> p.UsedCapacity).sum());

                if (wDayMap.get(wDayStr) > 0) {
                    wCalendarT =
                            wDayFloatMap.get(wDayStr).stream().min(Comparator.comparing(o -> o.CurrentDate)).get().CurrentDate;
                    if (wCalendarT.compareTo(wMinCalendar) < 0)
                        wMinCalendar = wCalendarT;
                    wCalendarT =
                            wDayFloatMap.get(wDayStr).stream().max(Comparator.comparing(o -> o.CurrentDate)).get().CurrentDate;
                    if (wCalendarT.compareTo(wMaxCalendar) > 0)
                        wMaxCalendar = wCalendarT;
                }
            }
            if (wCalendarT == null) {
                wMaxCalendar = wOMSOrder.PlanFinishDate;
                wMinCalendar = wOMSOrder.PlanReceiveDate;
            }

            //创建任务
            wAPSTaskPart = new APSTaskPart();
            wAPSTaskPart.PartEndTime = wMaxCalendar;
            wAPSTaskPart.PartType = wFPCRoutePart.PartType;
            wAPSTaskPart.RoutePartID = wRoutePartID;
            wAPSTaskPart.RoutePartName = wFPCRoutePart.Name;
            wAPSTaskPart.RoutePartOrder = wFPCRoutePart.OrderID;
            wAPSTaskPart.PartStartTime = wMinCalendar;
            wAPSTaskPart.LineID = wOMSOrder.LineID;
            wAPSTaskPart.LineName = wOMSOrder.LineName;
            wAPSTaskPart.OrderID = wOMSOrder.ID;
            wAPSTaskPart.OrderNo = wOMSOrder.Code;
            wAPSTaskPart.ProductCode = wOMSOrder.ProductCode;
            wAPSTaskPart.PlanDayNum = wDayMap;
            wAPSTaskPart.ProductID = wOMSOrder.ProductID;
            wAPSTaskPart.ShiftPeriod = wAPSShiftPeriod.getValue();
            wAPSTaskPart.StationID = wFPCRoutePart.StationID;
            wAPSTaskPart.StationCode = wFPCRoutePart.StationCode;
            wAPSTaskPart.StationName = wFPCRoutePart.StationName;
            wAPSTaskPart.WorkShopID = wFPCRoutePart.WorkShopID;
            wAPSTaskPart.WorkAreaID = wFPCRoutePart.WorkAreaID;
            wAPSTaskPart.WorkAreaCode = wFPCRoutePart.WorkAreaCode;
            wAPSTaskPart.WorkAreaName = wFPCRoutePart.WorkAreaName;
            wAPSTaskPart.WorkShopID = wFPCRoutePart.WorkShopID;
            wAPSTaskPart.WorkShopCode = wFPCRoutePart.WorkShopCode;
            wAPSTaskPart.WorkShopName = wFPCRoutePart.WorkShopName;
            wAPSTaskPart.Active = 1;
            wAPSTaskPart.Status = APSTaskStatus.Saved.getValue();
            wAPSTaskPart.ActNum = 0;
            wAPSTaskPart.PlantNum = wOMSOrder.FQTYPlan;
            wAPSTaskPart.PlanDayDetailNum = wDayFloatMap;

            wResult.add(wAPSTaskPart);
        }

        return wResult;
    }


    public List<APSTaskPart> CreateAPSTaskPartList(BMSEmployee wLoginUser, APSShiftPeriod wAPSShiftPeriod,
                                                   OMSOrder wOMSOrder, Map<Integer, FPCRoutePart> wOrderRoutePartMap,
                                                   List<APSTaskPartPoint> wAPSTaskPartPointList, Map<Integer,
            APSTaskPartPoint> wDoneTaskPartPointMap) {
        List<APSTaskPart> wResult = new ArrayList<>();

        if (wAPSTaskPartPointList == null || wAPSTaskPartPointList.size() == 0) {
            return wResult;
        }

        APSTaskPart wAPSTaskPart;
        FPCRoutePart wFPCRoutePart;
        int wEntityID = 0;

        Map<Integer, List<APSTaskPartPoint>> wTaskPartPointListMap
                = wAPSTaskPartPointList.stream().collect(Collectors.groupingBy(APSTaskPartPoint::getRoutePartID));


        List<APSTaskPartPoint> wTaskPartPointList;

        for (int wRoutePartID : wTaskPartPointListMap.keySet()) {
            //无排程结果，continue
            if (!wOrderRoutePartMap.containsKey(wRoutePartID))
                continue;

            wFPCRoutePart = wOrderRoutePartMap.get(wRoutePartID);
            wTaskPartPointList = wTaskPartPointListMap.get(wRoutePartID);


            Map<String, Float> wDayMap = new HashMap<>();

            Calendar wMaxCalendar = CalendarTool.getCalendar(2000, 0, 1);
            ;
            Calendar wMinCalendar = CalendarTool.getCalendar(2100, 0, 1);
            Calendar wCalendarT = Calendar.getInstance();
            for (APSTaskPartPoint wAPSTaskPartPoint : wTaskPartPointList) {

                if (wAPSTaskPartPoint.PartPointStartTime.compareTo(wMinCalendar) < 0)
                    wMinCalendar = wCalendarT;
                if (wAPSTaskPartPoint.PartPointEndTime.compareTo(wMaxCalendar) > 0)
                    wMaxCalendar = wCalendarT;
            }
            if (wCalendarT == null) {
                wMaxCalendar = wOMSOrder.PlanFinishDate;
                wMinCalendar = wOMSOrder.PlanReceiveDate;
            }

            //创建任务
            wAPSTaskPart = new APSTaskPart();
            wAPSTaskPart.PartEndTime = wMaxCalendar;
            wAPSTaskPart.PartType = wFPCRoutePart.PartType;
            wAPSTaskPart.RoutePartID = wRoutePartID;
            wAPSTaskPart.RoutePartName = wFPCRoutePart.Name;
            wAPSTaskPart.RoutePartOrder = wFPCRoutePart.OrderID;
            wAPSTaskPart.PartStartTime = wMinCalendar;
            wAPSTaskPart.LineID = wOMSOrder.LineID;
            wAPSTaskPart.LineName = wOMSOrder.LineName;
            wAPSTaskPart.OrderID = wOMSOrder.ID;
            wAPSTaskPart.ProductCode = wOMSOrder.ProductCode;
            wAPSTaskPart.ProductID = wOMSOrder.ProductID;
            wAPSTaskPart.OrderNo = wOMSOrder.Code;
            wAPSTaskPart.ShiftPeriod = wAPSShiftPeriod.getValue();
            wAPSTaskPart.StationID = wFPCRoutePart.StationID;
            wAPSTaskPart.StationCode = wFPCRoutePart.StationCode;
            wAPSTaskPart.StationName = wFPCRoutePart.StationName;
            wAPSTaskPart.WorkAreaID = wFPCRoutePart.WorkAreaID;
            wAPSTaskPart.WorkAreaCode = wFPCRoutePart.WorkAreaCode;
            wAPSTaskPart.WorkAreaName = wFPCRoutePart.WorkAreaName;
            wAPSTaskPart.WorkShopID = wFPCRoutePart.WorkShopID;
            wAPSTaskPart.WorkShopCode = wFPCRoutePart.WorkShopCode;
            wAPSTaskPart.WorkShopName = wFPCRoutePart.WorkShopName;
            wAPSTaskPart.Active = 1;
            wAPSTaskPart.Status = APSTaskStatus.Saved.getValue();
            wAPSTaskPart.ActNum = 0;
            wAPSTaskPart.PlantNum = wOMSOrder.FQTYPlan;
            wAPSTaskPart.TaskPartPointList.addAll(wTaskPartPointList);
            if (wDoneTaskPartPointMap != null && wDoneTaskPartPointMap.size() > 0) {
                wAPSTaskPart.TaskPartPointList.addAll(wDoneTaskPartPointMap.values().stream().filter(p -> p.RoutePartID == wRoutePartID)
                        .sorted(Comparator.comparing(o -> o.PartPointStartTime)).collect(Collectors.toList()));
            }
            wAPSTaskPart.TaskPartPointList.addAll(wTaskPartPointList);
            wResult.add(wAPSTaskPart);
        }

        return wResult;
    }

    /**
     * 获取最大支持产能
     * 根据后工序计算最大支持产能  可能不需要 使用瓶颈工序排程 每个工艺的每日最大产能会被固化
     * <p>
     * 调用会对产能产生改变 调用前产能数据先Clone
     *
     * @param wLoginUser
     * @param wStartDateTemp 前置分叉点或者已确定排程分叉点
     * @param wStartDate     总的计划开始日期
     * @param wRoutePartList
     * @param wRoutePartID
     * @return
     */
    public float GetPartMaxCapacity(BMSEmployee wLoginUser, Map<Integer, Calendar> wStartDateTemp,
                                    Calendar wStartDate, Calendar wDate,
                                    float wPlanNumber, APSMarginCapacityAndResult wAPSMarginCapacityAndResult,
                                    Map<Integer, FPCRoutePart> wRoutePartList, int wPrevPartID,
                                    FPCRoutePart wRoutePartID, boolean wIsDelay, int wAllowTechDelayHour) {


        if (!wRoutePartList.containsKey(wRoutePartID.ID))
            return 0;

        float wMarginCapacity = wAPSMarginCapacityAndResult.GetEntityCapacity(
                StringUtils.parseListArgs(wRoutePartID.getPartEntityID()), wRoutePartID.ProductID, wDate);

        if (wMarginCapacity > wPlanNumber)
            wMarginCapacity = wPlanNumber;

        if (wPlanNumber > wRoutePartID.FQTY) {
            if (wMarginCapacity < wRoutePartID.FQTY)
                wMarginCapacity = 0;
        }


        APSMarginCapacityAndResult wAPSMarginCapacityAndResultCloneT = wAPSMarginCapacityAndResult.Clone();

        //前序推演
        if (wMarginCapacity > 0) {
            //可能后序已经消减了前序的产能 需要确认前序产能
            wMarginCapacity = this.PrevPartMaxCapacity(wLoginUser, wStartDateTemp, wStartDate, wDate, wMarginCapacity,
                    wAPSMarginCapacityAndResultCloneT, wRoutePartList, wPrevPartID, wRoutePartID, wAllowTechDelayHour);
        }

        //后序推演
        if (wMarginCapacity > 0) {
            float wMarginCapacityT = 0f;
            Calendar wDateT;

            List<Integer> wNextPartIDs = this.GetNextPartList(wRoutePartID.ID, wRoutePartList);
            Map<Integer, Calendar> wPrevPartDate = wStartDateTemp;
            if (wNextPartIDs.size() > 1 && !wPrevPartDate.containsKey(wRoutePartID.ID)) {
                //分叉点回溯 分叉点下面执行的时候只有有一个日期的结果去运算或回溯，回溯的结果不能为多个
                wPrevPartDate = iPlantScheduleUtils.getInstance().CloneCalendar(wPrevPartDate);
                wPrevPartDate.put(wRoutePartID.ID, wDate);
                //排产回溯考虑确定性计划，排程使用确定性计划进入计算 得出最优解
                //考虑每次运行不一定能得到最好的结果，可能考虑支持强行排程问题，
                // 不支持强行 那么计划不能生成，
                // 支持强行 超期也能派出计划
            }

            APSMarginCapacityAndResult wAPSMarginCapacityAndResultClone = null;
            APSMarginCapacityAndResult wMinAPSMarginCapacityAndResult = null;

            for (Integer wPartID : wNextPartIDs) {
                if (!wRoutePartList.containsKey(wPartID))
                    continue;

                wAPSMarginCapacityAndResultClone = wAPSMarginCapacityAndResultCloneT.Clone();

                wDateT = CalendarTool.CloneDateTime(wDate);
                if (wRoutePartList.get(wPartID).MinPeriod > 0)
                    wDateT.add(Calendar.HOUR_OF_DAY, wRoutePartList.get(wPartID).MinPeriod);

                wMarginCapacityT = this.GetPartMaxCapacity(wLoginUser, wPrevPartDate, wStartDate, wDateT,
                        wMarginCapacity,
                        wAPSMarginCapacityAndResultClone, wRoutePartList, wRoutePartID.ID,
                        wRoutePartList.get(wPartID), true, wAllowTechDelayHour);

                if (wMinAPSMarginCapacityAndResult == null)
                    wMinAPSMarginCapacityAndResult = wAPSMarginCapacityAndResultClone;


                if (wMarginCapacityT <= wMarginCapacity) {
                    wMarginCapacity = wMarginCapacityT;
                    wMinAPSMarginCapacityAndResult.Set(wAPSMarginCapacityAndResultClone);
                }

            }

            if (wMarginCapacity > 0) {
                wAPSMarginCapacityAndResultCloneT.Set(wMinAPSMarginCapacityAndResult);
            }

        }


        // 消耗产能  增加日结果
        if (wMarginCapacity > 0) {
            //消减前序或后续产能
            wAPSMarginCapacityAndResult.Set(wAPSMarginCapacityAndResultCloneT);

            //消减今日产能
            wAPSMarginCapacityAndResult.UseCapacity(StringUtils.parseListArgs(wRoutePartID.getPartEntityID()),
                    wRoutePartID.ProductID,wRoutePartID.ID, wDate, wMarginCapacity);
            //增加排程计划
            wAPSMarginCapacityAndResult.AddResult(wRoutePartID.ID, wDate, wMarginCapacity);
        }

        // 计划延后
        if (wMarginCapacity < wPlanNumber && wIsDelay) {
            Calendar wDateN = CalendarTool.CloneDateTime(wDate);
            Calendar wDateC = CalendarTool.CloneDateTime(wDate);
            //平移每日产能

            for (int i = 1; i <= ((wRoutePartID.MaxPeriod + wAllowTechDelayHour) - wRoutePartID.MinPeriod); i++) {
                wDateN = CalendarTool.CloneDateTime(wDate);
                wDateN.add(Calendar.HOUR_OF_DAY, i);

                if (CalendarTool.CompareDate(wDateN, wDateC) == 0) {
                    continue;
                }
                wDateC = CalendarTool.CloneDateTime(wDateN);


                wMarginCapacity += this.GetPartMaxCapacity(wLoginUser, wStartDateTemp, wStartDate, wDateN,
                        wPlanNumber - wMarginCapacity,
                        wAPSMarginCapacityAndResult, wRoutePartList, wPrevPartID, wRoutePartID, false,
                        wAllowTechDelayHour);

                if (wMarginCapacity >= wPlanNumber)
                    break;
            }

        }
        if (wMarginCapacity < wPlanNumber) {
            wPlanNumber = wMarginCapacity;
        }

        return wPlanNumber;
    }


    public float GetPrevPartMaxCapacity(BMSEmployee wLoginUser, Map<Integer, Calendar> wPrevPartDate,
                                        Calendar wMinDate, Calendar wDate,
                                        float wPlanNumber, APSMarginCapacityAndResult wAPSMarginCapacityAndResult,
                                        Map<Integer, FPCRoutePart> wRoutePartList, FPCRoutePart wRoutePartID,
                                        int wAllowTechDelayHour) {


        if (!wRoutePartList.containsKey(wRoutePartID.ID))
            return 0.0f;

        float wMarginCapacity = wAPSMarginCapacityAndResult.GetEntityCapacity(
                StringUtils.parseListArgs(wRoutePartID.getPartEntityID()), wRoutePartID.ProductID, wDate);
        if (wMarginCapacity > wPlanNumber)
            wMarginCapacity = wPlanNumber;

        if (wMarginCapacity > 0) {

            wMarginCapacity = this.PrevPartMaxCapacity(wLoginUser, wPrevPartDate, wMinDate, wDate, wMarginCapacity,
                    wAPSMarginCapacityAndResult, wRoutePartList, 0, wRoutePartID, wAllowTechDelayHour);

        }


        return wMarginCapacity;

    }


    private float PrevPartMaxCapacity(BMSEmployee wLoginUser, Map<Integer, Calendar> wPrevPartDate, Calendar wMinDate
            , Calendar wDate,
                                      float wMarginCapacity, APSMarginCapacityAndResult wAPSMarginCapacityAndResult,
                                      Map<Integer, FPCRoutePart> wRoutePartList, int wPrevPartID,
                                      FPCRoutePart wRoutePartID, int wAllowTechDelayHour) {


        List<Integer> wPrevRoutePartIDList =
                CheckMergeRoutePart(wRoutePartList.values().stream().collect(Collectors.toList()), wRoutePartID.ID);

        if (wPrevRoutePartIDList.size() > 0) {
            if (wPrevPartID > 0) {
                wPrevRoutePartIDList.removeIf(p -> p == wPrevPartID);
            }
            if (wPrevRoutePartIDList.size() == 0)
                return wMarginCapacity;
            Calendar wDateN = CalendarTool.CloneDateTime(wDate);
            Calendar wDateC = CalendarTool.CloneDateTime(wDate);
            float wMarginCapacityT = 0f;

            APSMarginCapacityAndResult wAPSMarginCapacityAndResultClone = null;

            APSMarginCapacityAndResult wMinAPSMarginCapacityAndResult = null;

            for (Integer wPrevInteger : wPrevRoutePartIDList) {

                if (!wRoutePartList.containsKey(wPrevInteger))
                    continue;

                wAPSMarginCapacityAndResultClone = wAPSMarginCapacityAndResult.Clone();
                //前序推演扣减产能时 需要验证是否是前面已确定计划

                wDateC = CalendarTool.CloneDateTime(wDate);
                wMarginCapacityT = 0f;

                for (int i = wRoutePartID.MaxPeriod + wAllowTechDelayHour; i >= wRoutePartID.Period; i--) {


                    wDateN = CalendarTool.CloneDateTime(wDate);
                    wDateN.add(Calendar.HOUR_OF_DAY, -i);

                    if (CalendarTool.CompareDate(wDateN, wDateC) == 0) {
                        continue;
                    }
                    wDateC = CalendarTool.CloneDateTime(wDateN);

                    if (wAPSMarginCapacityAndResult.ResultMap.containsKey(wPrevInteger)) {
                        // 判定时间范围再已排程计划中的数量
                        wMarginCapacityT += wAPSMarginCapacityAndResult.getCapacity(wPrevInteger, wDateN);
                        continue;
                    }

                    if (wPrevPartDate.containsKey(wPrevInteger)) {
                        if (CalendarTool.CompareDate(wDateN, wPrevPartDate.get(wPrevInteger)) != 0)
                            continue;

                        //得到这个分叉点在这天可以执行，就不用再回溯了，因为数据来源也是这个分叉点的计划
                        //不需要继续回溯了  日期推后的并不会将分叉点重复加入 都是单独计算
                        wMarginCapacityT = wMarginCapacity;  //且产能可能已经被消耗了
                        break;
                    } else {

                        if (wDateN.compareTo(wMinDate) < 0)
                            break;
                    }
                    wMarginCapacityT += this.GetPrevPartMaxCapacity(wLoginUser, wPrevPartDate,
                            wMinDate, wDateN, wMarginCapacity - wMarginCapacityT, wAPSMarginCapacityAndResultClone,
                            wRoutePartList, wRoutePartList.get(wPrevInteger), wAllowTechDelayHour);

                    if (wMinAPSMarginCapacityAndResult == null)
                        wMinAPSMarginCapacityAndResult = wAPSMarginCapacityAndResultClone;

                    if (wMarginCapacityT >= wMarginCapacity)
                        break;
                }
                //消减产能


                if (wMarginCapacityT < wMarginCapacity) {
                    wMarginCapacity = wMarginCapacityT;
                    wMinAPSMarginCapacityAndResult = wAPSMarginCapacityAndResultClone;
                }


            }


            if (wMarginCapacity > 0) {
                wAPSMarginCapacityAndResult.Set(wMinAPSMarginCapacityAndResult);
            }

            if (wMarginCapacity > 0) {
                //消减今日产能
                wAPSMarginCapacityAndResult.UseCapacity(StringUtils.parseListArgs(wRoutePartID.getPartEntityID()),
                        wRoutePartID.ProductID,wRoutePartID.ID, wDate, wMarginCapacity);

                //增加排程计划
                wAPSMarginCapacityAndResult.AddResult(wRoutePartID.ID, wDate, wMarginCapacity);
            }
        }

        return wMarginCapacity;
    }


    /**
     * 是否是合并工艺
     *
     * @param wRoutePartList
     * @param wPartID
     * @return
     */
    private List<Integer> CheckMergeRoutePart(List<FPCRoutePart> wRoutePartList, Integer wPartID) {

        List<Integer> wPartIDList = new ArrayList<Integer>();

        for (FPCRoutePart wFPCRoutePart : wRoutePartList) {
            if (wPartID == wFPCRoutePart.ID && wFPCRoutePart.PrevPartID > 0) {

                wPartIDList.add(wFPCRoutePart.PrevPartID);
                continue;
            }

            if (wFPCRoutePart.NextPartID == null || wFPCRoutePart.NextPartID.size() == 0) {
                continue;
            }
            for (Integer wPartIDTemp : wFPCRoutePart.NextPartID) {

                if (wPartID == wPartIDTemp && !wPartIDList.contains(wFPCRoutePart.ID)) {
                    wPartIDList.add(wFPCRoutePart.ID);
                }
            }
        }
        return wPartIDList.stream().distinct().collect(Collectors.toList());
    }


    /**
     * 生成实体每日剩余产能数据
     *
     * @param wShiftListMap      key1 实体ID
     * @param wCalendarListMap   key1 实体ID
     * @param wAPSTaskPartList
     * @param wCapacityEntityMap key1 实体ID   key2 ProductID
     * @param wUnavailableMap    对比剩余时长给出剩余产能 或者直接使用多长时间的产能
     * @return
     */
    private Map<Integer, APSMarginEntityCapacity> GetMarginCapacityList(BMSEmployee wLoginUser, Map<Integer,
            List<FMCShift>> wShiftListMap, FPCRouteType wFPCRouteType,
                                                                        Map<Integer, List<CFGCalendar>> wCalendarListMap, List<APSTaskPart> wAPSTaskPartList,
                                                                        Map<Integer, Map<Integer, List<FPCCapacity>>> wCapacityEntityMap, Calendar wStartDate, Calendar wEndDate,
                                                                        Map<Integer, List<FMCUnavailable>> wUnavailableMap) {
        Map<Integer, APSMarginEntityCapacity> wResult = new HashMap<>();
        if (wCapacityEntityMap == null)
            return wResult;
        APSMarginEntityCapacity wAPSMarginEntityCapacity;
        Map<String, List<APSCapacityUsed>> wUseCapacityMap;


        // GetEntityUnavailableList

        List<FMCUnavailable> wEntityUnavailableList;

        for (Integer wEntityID : wCapacityEntityMap.keySet()) {
            if (!wShiftListMap.containsKey(wEntityID))
                wShiftListMap.put(wEntityID, new ArrayList<>());

            if (!wCalendarListMap.containsKey(wEntityID))
                wCalendarListMap.put(wEntityID, new ArrayList<>());

            wEntityUnavailableList = new ArrayList<>();
            if (wUnavailableMap.containsKey(wEntityID))
                wEntityUnavailableList = wUnavailableMap.get(wEntityID);

            wUseCapacityMap = new HashMap<>();
            for (APSTaskPart wAPSTaskPart : wAPSTaskPartList) {
                if (wAPSTaskPart == null || wAPSTaskPart.PlanDayDetailNum == null || wAPSTaskPart.PlanDayDetailNum.size() == 0)
                    continue;

                for (String wDateString : wAPSTaskPart.PlanDayDetailNum.keySet()) {

                    if (!wUseCapacityMap.containsKey(wDateString))
                        wUseCapacityMap.put(wDateString, new ArrayList<>());

                    wUseCapacityMap.get(wDateString).addAll(wAPSTaskPart.PlanDayDetailNum.get(wDateString));
                }
            }

            wAPSMarginEntityCapacity = new APSMarginEntityCapacity(wEntityID, wShiftListMap.get(wEntityID),
                    wCalendarListMap.get(wEntityID), wCapacityEntityMap.get(wEntityID), wUseCapacityMap,
                    wEntityUnavailableList, wStartDate, wEndDate);

            wResult.put(wEntityID, wAPSMarginEntityCapacity);
        }


        return wResult;
    }

    /**
     * 获取实体剩余产能
     *
     * @param wLoginUser
     * @param wFPCRouteType
     * @param wStartTaskList
     * @param wEntityIDList
     * @param wFPCProductMap
     * @param wStartDate
     * @param wEndDate
     * @param wAPSMessage
     * @return
     */
    public Map<Integer, APSMarginEntityCapacity> GetMarginCapacityList(BMSEmployee wLoginUser,
                                                                       FPCRouteType wFPCRouteType,
                                                                       List<APSTaskPart> wStartTaskList,
                                                                       List<Integer> wEntityIDList, Map<Integer,
            FPCProduct> wFPCProductMap,
                                                                       Map<Integer, List<FMCUnavailable>> wUnavailableMap, Calendar wStartDate, Calendar wEndDate, List<APSMessage> wAPSMessage) {
        //查询不同实体的班次信息
        Map<Integer, List<FMCShift>> wShiftListMap = new HashMap<>();
        //查询不同实体的日历班次
        Map<Integer, List<CFGCalendar>> wCalendarListMap = new HashMap<>();

        //查询不同实体对应每个产品的加工产能
        Map<Integer, Map<Integer, List<FPCCapacity>>> wCapacityEntityMap = new HashMap<>();


        iPlantScheduleUtils.getInstance().SetShiftInfo(wLoginUser, wFPCRouteType, wEntityIDList, wFPCProductMap,
                wStartDate, wEndDate, wShiftListMap, wCalendarListMap, wCapacityEntityMap, wAPSMessage);

        OutResult<Integer> wErrorCode = new OutResult<>(0);


        return this.GetMarginCapacityList(wLoginUser, wShiftListMap, wFPCRouteType, wCalendarListMap, wStartTaskList,
                wCapacityEntityMap, wStartDate, wEndDate, wUnavailableMap);
    }


    /**
     * 根据工艺路径获取其下道工艺任务列表
     *
     * @param wRoutePartList
     * @return
     */
    private List<Integer> GetNextPartList(int wRoutePartID,
                                          Map<Integer, FPCRoutePart> wRoutePartList) {

        List<Integer> wPartIDList = new ArrayList<Integer>();
        for (FPCRoutePart wFPCRoutePart : wRoutePartList.values()) {
            if (wFPCRoutePart.ID == wRoutePartID)
                continue;
            if (wFPCRoutePart.PrevPartID == wRoutePartID) {
                wPartIDList.add(wFPCRoutePart.ID);
            }
            if (wFPCRoutePart.ID == wRoutePartID) {
                wPartIDList
                        .addAll(wFPCRoutePart.NextPartID);
            }
        }

        return wPartIDList;
    }


    public void CheckPartMaterials(BMSEmployee wLoginUser, List<OMSOrder> wOrderList, List<APSTaskPart> wStartTaskList,
                                   List<Integer> wMaterilIDList, Map<Integer, FPCRoute> wOrderRouteMap,
                                   Map<Integer, Double> wWMSStockMap, boolean wCheckMaterial,
                                   List<APSMessage> wAPSMessage) {
        /**
         * 需求物料数  key物料 value 物料数
         * //todo  月计划只提示那些物料缺  生成物料需求计划时得出啥时候啥物料缺
         * //todo  周计划物料缺的订单不允许排程 优先满足前面的订单 后面的订单缺料就直接不检查了
         */
        Map<Integer, Double> wMarginMaterial = new HashMap<>();
        OutResult<Integer> wErrorCode = new OutResult<>(0);

        int wFactoryID = -1;
        List<Integer> wMaterialIDList = new ArrayList<>();
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wOrderRouteMap.containsKey(wOMSOrder.ID))
                continue;

            if (wOMSOrder.FactoryID > 0 ){
                if( wFactoryID < 0){
                    wFactoryID = wOMSOrder.FactoryID;
                }else if(wOMSOrder.FactoryID!=wFactoryID){
                    wFactoryID=0;
                }
            }

            for (FPCRoutePart wFPCRoutePart : wOrderRouteMap.get(wOMSOrder.ID).PartList) {

                for (FPCRoutePartPoint wFPCRoutePartPoint : wFPCRoutePart.PartPointList) {

                    for (MSSBOMItem wMSSBOMItem : wFPCRoutePartPoint.BOMItemList) {
                        if (wMaterialIDList.contains(wMSSBOMItem.MaterialID)) {
                            continue;
                        }
                        wMaterialIDList.add(wMSSBOMItem.MaterialID);
                    }
                }
            }

        }

        //获取
        //查询产品对应物料的库存
        List<WMSStock> wWMSStockList = WMSStockRecordDAO.getInstance().WMS_QueryStockCountList(wLoginUser,
                wFactoryID, wMaterialIDList, Pagination.getNewMaxSize(), wErrorCode);

        //todo 需要换算成BOM用量单位  每个物料都需要换算成BOM用量
        wWMSStockMap.putAll(wWMSStockList.stream().collect(Collectors.toMap(p -> p.MaterialID, p -> p.MaterialNum,
                (o1, o2) -> o2)));

        //todo 订单所有配料单
        //todo 存在 wStartTaskList 但是未配料库存  需要消减
        //todo 存在 已配料但是不在 wStartTaskList中的  需要增加对应配料的库存


        List<OMSOrder> wAllowOrder = new ArrayList<>();
        Map<Integer, Map<Integer, Double>> wNeedNumMap = new HashMap<>();
        boolean wIsAllow = false;
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wOrderRouteMap.containsKey(wOMSOrder.ID))
                continue;

            if (!wNeedNumMap.containsKey(wOMSOrder.RouteID)) {
                wNeedNumMap.put(wOMSOrder.RouteID, new HashMap<>());
                for (FPCRoutePart wFPCRoutePart : wOrderRouteMap.get(wOMSOrder.ID).PartList) {

                    for (FPCRoutePartPoint wFPCRoutePartPoint : wFPCRoutePart.PartPointList) {

                        for (MSSBOMItem wMSSBOMItem : wFPCRoutePartPoint.BOMItemList) {
                            if (wNeedNumMap.get(wOMSOrder.RouteID).containsKey(wMSSBOMItem.MaterialID)) {
                                wNeedNumMap.get(wOMSOrder.RouteID).put(wMSSBOMItem.MaterialID,
                                        wNeedNumMap.get(wOMSOrder.RouteID).get(wMSSBOMItem.MaterialID) + wMSSBOMItem.MaterialNumber);
                            } else {
                                wNeedNumMap.get(wOMSOrder.RouteID).put(wMSSBOMItem.MaterialID,
                                        wMSSBOMItem.MaterialNumber);
                            }
                        }
                    }
                }
            }

            // todo 对比 wNeedNumMap.get(wOMSOrder.RouteID) 与库存

            //todo 检查订单物料是否缺失
            //todo 检查BOP是否缺料

            if (!wIsAllow) {
                wAPSMessage.add(new APSMessage(APSMsgTypes.Capacity.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode,

                        StringUtils.Format("订单：{0} 排程时物料不足！", wOMSOrder.Code)));
                //todo 生成消息
                if (wCheckMaterial) {
                    continue;
                }
            }
            //todo 消减对应物料库存
            wAllowOrder.add(wOMSOrder);
        }
        wOrderList.clear();
        wOrderList.addAll(wAllowOrder);
    }


    public void CheckPlanPartMaterials(BMSEmployee wLoginUser, List<APSTaskPart> wStartTaskList,
                                       List<APSTaskPart> wPlanTaskList,
                                       List<Integer> wMaterilIDList, Map<Integer, FPCRoute> wOrderRouteMap,
                                       Map<Integer, Double> wWMSStockMap, List<APSMessage> wAPSMessage) {
        /**
         * 需求物料数  key物料 value 物料数
         * //todo  月计划只提示那些物料缺  生成物料需求计划时得出啥时候啥物料缺
         * //todo  周计划物料缺的订单不允许排程 优先满足前面的订单 后面的订单缺料就直接不检查了
         */
        Map<Integer, Double> wMarginMaterial = new HashMap<>();
        OutResult<Integer> wErrorCode = new OutResult<>(0);
        //获取
        //查询产品对应物料的库存
        List<WMSStock> wWMSStockList = WMSStockRecordDAO.getInstance().WMS_QueryStockCountList(wLoginUser, 0,wMaterilIDList, Pagination.getNewMaxSize(), wErrorCode);

        //todo 需要换算成BOM用量单位  每个物料都需要换算成BOM用量
        wWMSStockMap.putAll(wWMSStockList.stream().collect(Collectors.toMap(p -> p.MaterialID, p -> p.MaterialNum,
                (o1, o2) -> o2)));

        //todo 订单所有配料单
        //todo 存在 wStartTaskList 但是未配料库存  需要消减
        //todo 存在 已配料但是不在 wStartTaskList中的  需要增加对应配料的库存


        //todo 循环待排程计划 消减物料，哪一天物料开始缺，具体那些物料缺 影响哪些计划

    }
}
